An alternative idea of exporting ZK Charts

From Documentation
DocumentationSmall Talks2015AugustAn alternative idea of exporting ZK Charts
An alternative idea of exporting ZK Charts

Author
Vincent Jian, Engineer, Potix Corporation
Date
August 17, 2015
Version
ZK 7.0.5, ZK Charts 2.0.0.1

Introduction

ZK Charts is based on a 3rd party Javascript charting library called Highcharts, and by default ZK Charts integrates Highcharts Exporting Module so that end-users can easily export a chart to PNG. JPEG, TIFF and PDF format by selecting a predefined menu. However the exporting process will expose charts data to an external server that is hosted by Highcharts, which may introduce security concerns, if the data is sensitive. In addition the predefined results may not be what you needed -- you may wish to process the data on the server side based on your project requirements.

There are a few ways to address these issues; the first idea is to setup your own export server to export the chart following Highcharts Document. However sometimes, setting up and maintaining another server that is only used for exporting charts costs too much, so here we will introduce another approach to demonstrate how you can export ZK Charts step by step without hosting any export server.

Steps to Export ZK Charts

Before we start, I would like to first introduce a great tool that can convert between different image types with one simple command line: ImageMagick. And in the following part we will introduce how to export png/pdf file from ZK Chart without setting up an exporting server with this tool. You can download the portable version of the tool from ImageMagick download page. Here I use windows system, so I download the portable version ImageMagick-6.9.1-10-portable-Q16-x86.zip. After extracting the zip file, you can see a list of executable files, here we only need "converter.exe" file.

As we want to export chart within ZK web application, we need another java-based interface solution called im4java that can use ImageMagick command line tool in java application. You can download it from sourceforge. Here I use maven project so I add the maven dependency in pom.xml file as below,

<dependency>
	<groupId>org.im4java</groupId>
	<artifactId>im4java</artifactId>
	<version>1.4.0</version>
</dependency>

Now, we are ready to export ZK Charts.

Step 1 - Register ImageMagick executable file path

  • Put the converter.exe file extracted from the zip file we downloaded under any folder you like.
  • Create ExportChartWebAppInit.java to register ImageMagick.
public class ExportChartWebAppInit implements WebAppInit {
	public void init(WebApp wapp) throws Exception {
		// set the converter.exe file path for im4java
		// converter.exe file is downloaded from ImageMagick (http://www.imagemagick.org/script/binary-releases.php)
		String magickPath = "C://image-magick";
		ProcessStarter.setGlobalSearchPath(magickPath);
	}
}
  • Add ExportChartWebAppInit.java listener in zk.xml file.
<!-- in WEB-INF/zk.xml file -->
<listener>
	<listener-class>demo.init.ExportChartWebAppInit</listener-class>
</listener>

Step 2 - Retrieve chart content from Highchart and send it back to server

Highcharts is a Javascript charting library displayed on the browser with SVG content, it also provides an getSVG() API to get the SVG content. Here we create a javascript function called fireExportEvent to send the SVG content to server as follows:

function fireExportEvent (wgt, id) {
	var svg = zk.Widget.$('$' + id).engine.getSVG();
	zAu.send(new zk.Event(wgt, 'onExport', {svg: svg}, {toServer: true}));
}
  • In line 2, we first find the chart widget with zk.Widget.$() API with id and get Highchart object with chart.engine then we can call the getSVG() API to get the SVG content.
  • In line 3, we send the SVG content with a custom event onExport to server, then we can manipulate the data later.

Then in zul page, we create a button to execute fireExportEvent function.

<zk xmlns:w="client">
	<script src="/js/export.js"></script>
	<window apply="demo.ctrl.ExportComposer">
		<charts id="chart" type="column" />
		<hlayout>
			<listbox mold="select" id="format">
				<listitem label="svg" selected="true" />
				<listitem label="png" />
				<listitem label="jpg" />
				<listitem label="tiff" />
				<listitem label="pdf" />
			</listbox>
			<button id="export" label="Export" w:onClick="fireExportEvent(this, 'chart')" />
		</hlayout>
	</window>
</zk>
  • Line 2, include the export javascript function created previously.
  • Line 3, register client onclick event for the button and call the javascript function.

Step 3 - Export the chart

In previous step, we send the onExport event to the button component, now we can manipulate SVG content to convert it to png/jpeg/pdf file, or just export the SVG file directly if end user needs unprocessed image.

1. Create a temp file and write the SVG content to the file.

@Listen("onExport = #export")
public void export(Event evt) throws IOException {
	String fmt = format.getSelectedItem().getLabel();
	String fileName = chart.getTitle().getText().toLowerCase().replaceAll(" ", "_");
	String tDir = System.getProperty("java.io.tmpdir");
	Path svgFile = Paths.get(tDir, fileName + ".svg");
	Files.deleteIfExists(svgFile);
	Map<String, String> data = (Map<String, String>) evt.getData();
	String svgContent = data.get("svg");
	svgFile = Files.createFile(svgFile);
	Files.write(svgFile, svgContent.getBytes());

	if (fmt.equals("svg")) {
		Filedownload.save(svgFile.toFile(), null);
	}
}
  • Line 10, 11: create temp file and write SVG content.
  • Line 14: If user wants the raw data, he can download the file directly.

2. Convert the SVG file to another format follow Developer's Guide from im4java.

@Listen("onExport = #export")
public void export(Event evt) throws IOException {

	// omitted svgFile creation part

	if (fmt.equals("svg")) {
		Filedownload.save(svgFile.toFile(), null);
	} else {
		Path output = Paths.get(tDir, fileName + "." + fmt);
		Files.deleteIfExists(output);

		IMOperation op = new IMOperation();
		op.addImage(svgFile.toFile().getPath());
		op.addImage(output.toFile().getPath());

		ConvertCmd converter = new ConvertCmd();
		try {
			converter.run(op);
			Filedownload.save(output.toFile(), null);
		} catch (InterruptedException | IM4JavaException e) {
			e.printStackTrace();
		}
	}
}
  • Line 12-14: we create an image operation instance and then specify the input and output file to the operation.
  • Line 16,18: we create a converter command and run the operation to do the conversion.

Advanced Usage - Export multiple Charts to one File

According to Highchart's FAQ, it is possible to export multiple charts to one file. And this is done by merging multiple SVG contents with the hack on jsfiddle. Here we will introduce how to integrate the hack with the javascript function we created previously.

First, the hack on jsfiddle is used for native Highchart object, so here we just modify it a bit to fit ZK Client Engine as follows:

zk.afterMount(function() {
	Highcharts.getMergedSVG = function(charts) {
		var svgArr = [],
			top = 0,
			width = 0;

		jq.each(charts, function(i, chart) {
			var svg = chart.getSVG();
			svg = svg.replace('<svg', '<g transform="translate(0,' + top + ')" ');
			svg = svg.replace('</svg>', '</g>');

			top += chart.chartHeight;
			width = Math.max(width, chart.chartWidth);

			svgArr.push(svg);
		});

		return '<svg height="' + top + '" width="' + width
				+ '" version="1.1" xmlns="http://www.w3.org/2000/svg">'
				+ svgArr.join('') + '</svg>';
	};
});

In line 7, we use the jquery integrated in ZK Client Engine to avoid jquery version conflict (that is use jq.each() instead of $.each()).

Second, modify the fireExportEvent function we created to allow passing multiple chart widgets:

function fireExportEvent (wgt, id) {
	if (!id)
		return; // nothing to export
	
	var svg;
	if (jq.isArray(id)) {
		var charts = [];
		for (var i = 0, len = id.length; i < len; i++) {
			charts[i] = zk.Widget.$('$' + id[i]).engine;
		}
		svg = Highcharts.getMergedSVG(charts);
	} else {
		svg = zk.Widget.$('$' + id).engine.getSVG();
	}
	zAu.send(new zk.Event(wgt, 'onExport', {svg: svg}, {toServer: true}));
}

In line 6, here we check if the variable "id" is an array, if it is an array then the svg content should be merged by the hack function (line 11).

Finally, in the zul page we create another button to export two charts into one file:

<zk xmlns:w="client">
	<script src="/js/export.js"></script>
	<window apply="demo.ctrl.ExportComposer">
		<charts id="chart1" type="column" width="600" />
		<charts id="chart2" type="line" width="600" />
		<hlayout>
			<listbox mold="select" id="format">
				<listitem label="svg" selected="true" />
				<listitem label="png" />
				<listitem label="jpg" />
				<listitem label="tiff" />
				<listitem label="pdf" />
			</listbox>
			<button id="export1" label="Export Column Chart" w:onClick="fireExportEvent(this, 'chart1')" />
			<button id="export2" label="Export Line Chart" w:onClick="fireExportEvent(this, 'chart2')" />
			<button id="exportAll" label="Export All" w:onClick="fireExportEvent(this, ['chart1', 'chart2'])" />
		</hlayout>
	</window>
</zk>

Line 16, the argument passed to the function is an array of chart id "chart1" and "chart2".

Conclusion

In this article we shared an idea on how you can pass the SVG charting content to server side, so that you can manipulate it for all kinds of requirements such as converting, merging and exporting to another format. With this approach you do not need to host any exporting server which also reduces the maintenance cost.

Download

You can find the complete sample code on github. Note that all tools, libraries used in this small talk belongs to their creators and are licensed under their corresponding licenses.


Comments



Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.